/**

This class provides the following to the EditorGridPanel (could be changed to GridPanel):

    * Handles showing an associated menu as a context menu
    * Binds the return key to the grid providing a single click and double click using the return key with the shift key
    * Right clicking on a row if not selected will trigger the click event and highlight the row
    * Clears the selection if a right click isn't on a row
    * Handles adding, inserting and removing a record from the grid
    * Introduces the defaultValue with each record field and used when adding a record to the grid
    * Information from the Store is used to prepare and send requests back to the server when being committed
    * Keeps track of records that have been deleted from the grid and used later when committing it back to the server
    * Commit the changes to the server and subscribe to the success or failure events
    * Fires an event when all the modified and deleted records have been committed back to the server
    * A function to see if the grid is dirty (has deleted and/or modified records)
    * Added completed event to the onEditComplete function so your program can validate the content in the cell after the change, but before it's committed to the record even if nothing changed. This is handy to clear a new row/record if nothing has changed.

see http://extjs.com/forum/showthread.php?t=19209

**/


Ext.namespace('Ext.ux');

Ext.ux.ExtendedGridPanel = function(config) {
    Ext.apply(this, config);

    // initialize array to store deleted records
    this.deleted = [];

    Ext.ux.ExtendedGridPanel.superclass.constructor.call(this, config);
};
Ext.extend(Ext.ux.ExtendedGridPanel, Ext.grid.EditorGridPanel, {
    initComponent : function() {
        if (!this.record) {
            // if record isn't defined, use the record definition from the store
            this.record = this.store.recordType;
        }

        if (!this.updateUrl && this.store.proxy) {
            // if updateUrl isn't defined, use the url from teh store
            this.updateUrl = this.store.proxy.conn.url;
        }

        Ext.ux.ExtendedGridPanel.superclass.initComponent.call(this);
    },

    onRender : function(ct, position) {
        Ext.ux.ExtendedGridPanel.superclass.onRender.apply(this, arguments);

        // bind a few useful events to handle row selection, and context menu
        this.on('contextmenu', this.showContextMenu, this);
        this.on('rowcontextmenu', this.onRowContextMenu, this);
        this.store.on('load', this.onLoad, this);

        // relay these handy events to allow.  can be used for controlling enable/disable of 
        // menus, buttons, etc.
        this.relayEvents(this.view, ['rowselect', 'rowdeselect']);
        this.relayEvents(this.selModel, ['clearselect']);

        // map the return key to the grid to provide key click and double click handling
        this.keymap = new Ext.KeyMap(this.el, { key: 13, fn: this.handleKeys, scope: this });
    },

    // this function manages the return key events
    handleKeys : function(key, e) {
        e.stopEvent();
        switch(key) {
            case 13:
                // get the row that is highlighted
                var rowIndex = this.selModel.last;
                // determine if this should be a double click or a single click
                var keyEvent = (e.shiftKey === true) ? 'rowdblclick' : 'rowclick';
                // fire the determined event
                this.fireEvent(keyEvent, this, rowIndex, e);
                break;
        }
    },

    // this function handles row selection and context menu
    showContextMenu : function(e, rowIndex) {
        e.stopEvent();
        if (rowIndex === undefined) {
            // if no row was selected from right clicking, clear the selections
            this.selModel.clearSelections();
        }
        if (this.menu) {
            // if the menu has been set, show it at the XY location
            this.menu.showAt(e.getXY());
        }
    },

    // this function handles row selection and context menu
    onRowContextMenu : function(grid, rowIndex, e) {
        e.stopEvent();
        if (! this.selModel.isSelected(rowIndex)) {
            // if the row right clicked on isn't selected already, select it 
            // and fire the event
            this.selModel.selectRow(rowIndex);
            this.fireEvent('rowclick', this, rowIndex, e);
        }
        // call the showContextMenu function for further processing
        this.showContextMenu(e, rowIndex);
    },

    // this function handles what happens after loading of the local store
    onLoad : function(store, record, options) {
        // clear the deleted and modified record arrays
        this.deleted = [];
        this.store.modified = [];
        // fire a custom load event passing the grid and the usual store load
        // parameters
        this.fireEvent('load', this, store, record, options);
    },

    // this function handles adding a record to the grid
    addRecord : function(fields) {
        // get the index of the last position in the grid
        var rowIndex = this.store.data.length;
        // call insertRecord for further processing
        this.insertRecord(rowIndex, fields);
        // return the row where the record was added
        return rowIndex;
    },

    // this function handles inserting a record into the grid
    insertRecord : function(rowIndex, fields) {
        // get a list of record fields
        var f = this.record.prototype.fields;
        var defaultValues = [];
        for(var i = 0; i < f.length; i++) {
            // loop through the fields gathering a list of default values
            // to use when creating the initial record
            defaultValues[f.items[i].name] = f.items[i].defaultValue;
        }

        // create a new record object passing the default values
        var record = new this.record(defaultValues);

        this.stopEditing();
        // insert the record into the store
        this.store.insert(rowIndex, record);
        for (var v in fields) {
            // loop through the passed in fields setting the new values
            record.set(v, fields[v]);
        }
    },

    // this function handles removing a record from the grid
    removeRecord : function(record) {
        // ensure that the record is in fact a record
        if (record instanceof Ext.data.Record){
            if (record.dirty) {
                // if the record is dirty (not yet in the database), reje
                // ct the changes
                record.reject();
            } else {
                // keep track of the records that have been deleted and 
                // where the record has already been created in the database
                this.deleted.push(record);
            }

            // remove the record from the store
            this.store.remove(record);
            if (this.selModel.getSelections().length === 0) {
                // if no more records are selected, fire the event.  useful
                // when you want to control enable/disable of buttons, 
                // menus, etc.
                this.fireEvent('clearselect', this);
            }
        }
    },

    // this function prepares a list of parameters to send back to the server
    getParams : function(record) {
        // append the record's data, and the store's baseParams and lastOptions values to create
        // a list of parameters to be used to send information back to the server
        var params = Ext.apply(record.data, this.store.baseParams, this.store.lastOptions.params);
        return params;
    },

    // this function handles committing the records back to the server
    commitRecord : function(record, action) {
        // get the records id field name
        var id = this.store.reader.meta.id;
        // prepare the list of parameters
        var params = this.getParams(record);
        // create a connection object with the action type: update/delete
        var conn = new Ext.data.Connection({
            extraParams: { action: action }
        });
        // send request to the server
        conn.request({
            url: this.updateUrl,
            // get the method used by the store
            method: this.store.proxy.conn.method,
            scope: this,
            params: params,
            // handle the successful transaction
            success: function(r, options) {
                // read in the response from the server
                var data = Ext.util.JSON.decode(r.responseText);
                if (data.success) {
                    if (action == 'update') {
                        // set the record's new unique ID field that was created/updated on the server
                        record.set(id, data[id]);
                        // commit that change
                        record.commit();
                        // update the id of the record with the unique ID from the server
                        record.id = data[id];
                    } else {
                        // remove the record from the deleted array
                        this.deleted.remove(record);
                    }
                    // fire an event after each record is committed successfully
                    this.fireEvent('committed', record, action);
                    if (! this.isDirty()) {
                        // if there are no more files to be updated/removed, fire an event so further
                        // processing can be performed in the application
                        this.fireEvent('commitdone', this);
                    }
                } else {
                    // if there was a failure, fire an event
                    this.fireEvent('commitfailed', record, action, data.error);
                }
            },
            failure: function(r, options) {
                // if there was a failure, fire an event
                this.fireEvent('commitfailed', record, action, 'Problem calling ' + this.updateUrl);
            }
        });
    },
    // this function handles saving all deleted and modified records to the server
    saveRecords : function() {
        for (var i = 0; i < this.deleted.length; i++) {
            // loop through the deleted records and call commitRecord with the record and action
            this.commitRecord(this.deleted[i], 'delete');
        }
        var modified = this.store.getModifiedRecords();
        for (var i = 0; i < modified.length; i++) {
            // loop through the modified records and call commitRecord with the record and action
            this.commitRecord(modified[i], 'update');
        }
    },

    // this function checks both the deleted and modified arrays for any pending changes
    isDirty : function() {
        if (this.deleted.length > 0  || this.store.modified.length > 0) {
            return true;
        }
        return false;
    },

    // the next two functions are just handy shortcuts
    refreshGrid : function(options) {
        this.store.reload(options);
    },

    clearSelections : function() {
        this.selModel.clearSelections();
    }
});


// the following are only here to add a few handy events
Ext.grid.RowSelectionModel.prototype.clearSelections = function(fast) {
    if (this.locked) return;
    if (fast !== true) {
        var ds = this.grid.store;
        var s = this.selections;
        s.each(function(r) {
           this.deselectRow(ds.indexOfId(r.id));
        }, this);
        s.clear();
    } else {
        this.selections.clear();
    }
    this.last = false;
    this.fireEvent('clearselect', this);
};

Ext.grid.GridView.prototype.onRowSelect = function(row) {
    this.addRowClass(row, "x-grid3-row-selected");
    this.fireEvent('rowselect', 'select', row);
};
Ext.grid.GridView.prototype.onRowDeselect = function(row) {
    this.removeRowClass(row, "x-grid3-row-selected");
    this.fireEvent('rowdeselect', 'deselect', row);
};


Ext.grid.EditorGridPanel.prototype.onEditComplete = function(ed, value, startValue) {
    this.editing = false;
    this.activeEditor = null;
    ed.un("specialkey", this.selModel.onEditorKey, this.selModel);
    if(String(value) !== String(startValue)){
        var r = ed.record;
        var field = this.colModel.getDataIndex(ed.col);
        var e = {
            grid: this,
            record: r,
            field: field,
            originalValue: startValue,
            value: value,
            row: ed.row,
            column: ed.col,
            cancel:false
        };
        if(this.fireEvent("validateedit", e) !== false && !e.cancel){
            r.set(field, e.value);
            delete e.cancel;
            this.fireEvent("afteredit", e);
        }
    }
    this.view.focusCell(ed.row, ed.col);
    this.fireEvent('completed', ed, value, startValue);
};